package net.atomarrow.services;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.atomarrow.annotation.Inject;
import net.atomarrow.annotation.UseSetMethod;
import net.atomarrow.bean.Pager;
import net.atomarrow.configs.Config;
import net.atomarrow.configs.DbConfig;
import net.atomarrow.db.executor.JdbcExecutor;
import net.atomarrow.db.orm.Attribute;
import net.atomarrow.db.orm.IDomainControl;
import net.atomarrow.db.orm.OrmContext;
import net.atomarrow.db.orm.listener.OrmListener;
import net.atomarrow.db.orm.listener.OrmListenerKeeper;
import net.atomarrow.db.parser.Conditions;
import net.atomarrow.db.parser.JdbcParser;
import net.atomarrow.db.parser.batch.BatchSqlHolder;
import net.atomarrow.domains.Domain;
import net.atomarrow.util.EnumUtil;
import net.atomarrow.util.StringUtil;

public class JdbcService implements IService {
    @Inject
    private JdbcParser parser;
    @Inject
    private JdbcExecutor operator;
    @Inject
    private OrmListenerKeeper ormListenerHolder;

    @Override
    public boolean checkExist(Class clazz, String colName, Serializable value,
            Serializable exceptId) {
        return this.getCount(new Conditions(getTableName(clazz)).putEW(colName, value).putUE("id", exceptId))>0;
    }
    @Override
    public boolean checkExist(String tableName, String colName, Serializable value,
            Serializable exceptId) {
        return this.getCount(new Conditions(tableName).putEW(colName, value).putUE("id", exceptId))>0;
    }
	@Override
	public boolean addByBatch(List<? extends Domain> dataList) {
		if (dataList == null || dataList.size() == 0)
			return false;
		return addByBatch(getTableName(dataList.get(0)), dataList);
	}

	@Override
	public <T extends Domain> boolean addByBatch(Class<T> clazz,
			final List<T> dataList) {
		return addByBatch(getTableName(clazz), dataList);
	}

	public  boolean addByBatch(String tableName, final List<? extends Domain> dataList) {
		if (dataList == null || dataList.size() == 0)
			return false;
		BatchSqlHolder sqlHolder = parser.getBatchAddSql(tableName,dataList);
		return operator.batchUpdate(sqlHolder);
	}


	@Override
	public int del(Domain domain) {
		return delById(getTableName(domain), (Serializable) getValue("id", domain));
	}
	private Object getValue(String name, Object object) {
		Field f;
		try {
			f = object.getClass().getDeclaredField(name);
			f.setAccessible(true);
			return f.get(object);
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}
	@Override
	public int delByBatch(Conditions conditions) {
		return operator.update(parser.getDelHql(conditions));
	}

	@Override
	public <T> int delById(Class<T> clazz, Serializable id) {
		return delById(getTableName(clazz), id);
	}

	@Override
	public <T> int delById(String className, Serializable id) {
		return operator.update(parser.getDelHql(new Conditions(className).putEW(
				"id", id)));
	}

	@Override
	public int executeUpdate(String sql, Object[] params) {
		if(params==null){
			return operator.update(sql);
		}else{
			return operator.update(sql, params);
		}
	}


	@Override
	public <T> T getById(Class<T> clazz, Serializable id) {
		return getById(getTableName(clazz), clazz, id);
	}

	@Override
	public <T> List<T> getColumnList(Conditions conditions, String attribute) {
		return (List<T>) operator.queryForList(parser
				.getColumnHql(conditions, attribute),Object.class);
	}
	
	@Override
	public <T> List<T> getColumnDistinctList(Conditions conditions, String attribute) {
		return getColumnList(conditions, "distinct "+attribute);
	}

	@Override
	public <T> List<T> getColumnListByPage(Conditions conditions,
			String attribute, Pager pager) {
		return (List<T>) operator.queryForList(parser
				.getColumnPagerSql(conditions, attribute, pager),Object.class);
	}
	
	@Override
	public <T> List<T> getColumnDistinctListByPage(Conditions conditions,
			String attribute, Pager pager) {
		return getColumnListByPage(conditions, "distinct "+attribute, pager);
	}

	@Override
	public int getCount(Conditions conditions) {
		Integer count=operator.queryForInt(parser.getCountHql(conditions));
		return count==null?0:count.intValue();
	}
	@Override
	public int getDistinctCount(Conditions conditions, String attribute) {
			Integer count=operator.queryForInt(parser.getDistinctCountHql(conditions,attribute));
			return count==null?0:count.intValue();
	}

	@Override
	public <T> List<T> getDistinctList(Conditions conditions, String attribute) {
		return (List<T>) operator.queryForList(parser
				.getDistinctHql(conditions, attribute),Object.class);
	}
	
   @Override
    public <T> List<T> getDistinctListPage(Conditions conditions,String attribute,Pager pager) {
        List<Map<String, Object>> results = operator.queryForList(parser
                .getDistinctHqlPagerSql(conditions,attribute, pager));
        return generateObjectList(getReturnClass(conditions), results);
    }

   @Override
   public List<Map<String, Object>> getMapList(Conditions conditions) {
	   List<Map<String, Object>> results = operator.queryForList(parser
			   .getSelectHql(conditions));
	   return results;
   }
	@Override
	public <T> List<T> getList(Conditions conditions) {
		List<Map<String, Object>> results = operator.queryForList(parser
				.getSelectHql(conditions));
		return generateObjectList(getReturnClass(conditions), results);
	}

	private List generateObjectList(Class returnClass,
			List<Map<String, Object>> results) {
		if (results.size() == 0)
			return Collections.EMPTY_LIST;
		List<Attribute> attributes = OrmContext.getAttributes(returnClass
				.getSimpleName());
		ArrayList objects = new ArrayList(results.size());
		int size = results.size();
		for (int i = 0; i < size; i++) {
			objects
					.add(generateObject(returnClass, attributes, results.get(i)));
		}
		return objects;
	}

	private <T> T generateObject(Class<T> returnClass,
			List<Attribute> attributes, Map<String, Object> result) {
		if (result == null || result.size() == 0)
			return null;
		T obj = null ;
		boolean isDomain = Domain.class.isAssignableFrom(returnClass);
	    Method addUpdateFiledMethod=null,setUpdateNotNeedCheck=null;
		try {
			if(isDomain){
			    addUpdateFiledMethod=returnClass.getMethod("addUpdateField", String.class);
			    setUpdateNotNeedCheck=returnClass.getMethod("setUpdateNotNeedCheck", Boolean.class);
			}
		    obj = returnClass.newInstance();
			Object value;
			OrmListener listener = ormListenerHolder.getListener(returnClass);
			String fieldName;
			for (Attribute at : attributes) {
			    fieldName=at.getName();
	            if(listener!=null){
	                fieldName=listener.changeFieldName(returnClass, at.getName());
	            }
				value=result.get(fieldName);
			    if(listener!=null){
			        value=listener.changeValue(returnClass, at.getName(),obj,value);
                }
				if(value!=null){
					Field f = obj.getClass().getDeclaredField(fieldName); 
					f.setAccessible(true); 
					if(needSetMethod(f)){
                    	Method method = obj.getClass().getMethod("set"+StringUtil.toUpperCaseFirstWord(fieldName), f.getType());
                    	if(method==null){
                    		new Exception("set"+StringUtil.toUpperCaseFirstWord(fieldName)+" method not found");
                    	}
                    	method.invoke(obj,changeTypeIfNecessary(f,value));
	                }else{
	                	f.set(obj, changeTypeIfNecessary(f,value)); 
	                }
				}
				if(isDomain){
					addUpdateFiledMethod.invoke(obj, fieldName);
					setUpdateNotNeedCheck.invoke(obj, true);
				}
				
			}
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		}
		return obj;
	}

	private boolean needSetMethod(Field f) {
		return f.isAnnotationPresent(UseSetMethod.class);
	}
	private Object changeTypeIfNecessary(Field f, Object value) {
        if((f.getType()==Double.TYPE||f.getType()==Double.class)&&value.getClass()==BigDecimal.class){
            BigDecimal bd=(BigDecimal) value;
            return bd.doubleValue();
        }
        if((f.getType()==Integer.TYPE||f.getType()==Integer.class)&&value.getClass()==BigDecimal.class){
            BigDecimal bd=(BigDecimal) value;
            return bd.intValue();
        }
        if((f.getType()==Integer.TYPE||f.getType()==Integer.class)&&value.getClass()==Long.class){
        	Long l=(Long)value;
        	return l.intValue();
        }
        if((f.getType()==Integer.TYPE||f.getType()==Integer.class)&&value.getClass()==Double.class){
            Double d=(Double)value;
            return d.intValue();
        }
        if((f.getType()==Long.TYPE||f.getType()==Long.class)&&value.getClass()==BigDecimal.class){
            BigDecimal bd=(BigDecimal) value;
            return bd.longValue();
        }
        if((f.getType()==Boolean.TYPE||f.getType()==Boolean.class)&&value.getClass()==Long.class){
        	return value != null && (Long)value>0;
        }
        if((f.getType()==Boolean.TYPE||f.getType()==Boolean.class)&&value.getClass()==Integer.class){
        	return value != null && (Integer)value>0;
        }

        if (f.getType().isEnum()) {
        	return EnumUtil.getValueByName(f.getType(), value.toString());
        }
        
        return value;
    }


	@Override
	public <T> List<T> getListByPage(Conditions conditions, Pager pager) {
		if(pager==null){
			return getList(conditions);
		}
		if(pager.getDataTotal()==0){
			pager.setDataTotal(getCount(conditions));
		}
		List<Map<String, Object>> results = operator.queryForList(parser
				.getSelectPagerSql(conditions, pager));
		return generateObjectList(getReturnClass(conditions), results);
	}

	private Class getReturnClass(Conditions conditions) {
		Class returnClass=conditions.getReturnClass();
		if(returnClass==null){
			returnClass=OrmContext.getDomainClass(conditions.getReturnClassName());
		}
		return returnClass;
	}

	@Override
	public <T> List<T> getListWithColumn(Conditions conditions, String[] columns) {
		List<Map<String, Object>> results = operator.queryForList(parser.getMulColumnHql(conditions, columns));
		return generateObjectList(getReturnClass(conditions), results);
	}

	@Override
	public <T> List<T> getListWithColumnByPage(Conditions conditions,
			String[] columns, Pager pager) {
		if(pager==null){
			return getListWithColumn(conditions, columns);
		}
		if(pager.getDataTotal()==0){
			pager.setDataTotal(getCount(conditions));
		}
		List<Map<String, Object>> results = operator.queryForList(parser.getMulColumnPagerSql(conditions, columns, pager));
		return generateObjectList(getReturnClass(conditions), results);
	}

	@Override
	public Double getMax(Conditions conditions, String attribute) {
	    conditions.setSelectValue("max("+attribute+")");
	    Double max = getDouble(conditions);
		return max==null?0:max;
	}

	@Override
	public <T> T getMaxDomain(Conditions conditions, String attribute) {
	    conditions.putDESC(attribute);
	    return getOne(conditions);

	}

	private Map<String, Object> queryForMap(String sql) {
		return operator.queryForMap(sql);
	}

	@Override
	public <T> T getOne(Conditions conditions) {
		return (T) generateObject(getReturnClass(conditions), OrmContext
				.getAttributes(conditions.getReturnClassName()),
				queryForMap(parser
						.getSelectPagerSql(conditions, new Pager(0, 1))));
	}


	@Override
	public double getSum(Conditions conditions, String attribute) {
		Double sum=operator.queryForObject(parser.getSumHql(conditions,
				attribute), Double.class);
		if(sum==null){
			return 0;
		}
		return sum;
	}

	@Override
	public int modify(Domain domain) {
		return modify(getTableName(domain), domain);
	}
	
	@Override
	public boolean modifyByBatch(List<? extends Domain> dataList) {
		if(dataList==null||dataList.size()==0)return false;
		return modifyByBatch(getTableName(dataList.get(0)),dataList);
	}

	@Override
	public boolean modifyByBatch(String tableName,List<? extends Domain> dataList) {
		if(dataList==null||dataList.size()==0)return false;
		Domain domain = dataList.get(0);
		if(domain.getUpdateNotNeedCheck()==true&&DbConfig.getDatabaseType()==DbConfig.DB_TYPE_MYSQL){
			//数据库出来的list，所以可以采用批量更新，不会有字段丢失
			BatchSqlHolder sqlHolder = parser.getBatchUpdateSql(tableName,dataList);
			return operator.batchUpdate(sqlHolder);
//			operator.batchUpdate(sql, new JdbcBatchSetter(tableName, dataList,JdbcBatchSetter.MODE_UPDATE));
		}else{
			//可能是页面过来的批量更新，为了保留字段内容，不因为页面传过来的null影响，采用单个更新模式
			for(Domain data:dataList){
			    modify(tableName,data);
			}
		}
		return true;
	}

	@Override
	public int modifyWithColumn(Conditions conditions,
			Map<String, Serializable> columns) {
		return operator.update(parser.getModifyColumnHql(conditions, columns));
	}

	@Override
	public int modifyWithColumn(Conditions conditions,
			Serializable[] keyValues) {
		return operator.update(parser.getModifyColumnHql(conditions, keyValues));
	}

	@Override
	public boolean saveOrUpdate(List<? extends Domain> datas) {
		if(datas==null||datas.size()==0)return false;
		return saveOrUpdate(getTableName(datas.get(0)), datas);
	}

	/**
	 * 检查是否存在id 存在返回true
	 * 
	 * @param object
	 * @return
	 */
	private boolean checkIdExist(Object object) {
		Object id = getValue("id", object);
		return id != null && !id.equals("") && !id.equals(0);
	}

	@Override
	public void saveOrUpdate(Domain domain) {
		if(domain==null)throw new RuntimeException("saveOrUpdate data is Null");
		saveOrUpdate(getTableName(domain), domain);
	}

	@Override
	public Serializable add(Domain domain) {
		return add(getTableName(domain), domain);
	}

	@Override
	public Serializable add(final String tableName, final Domain data) {
	    String primaryKey="id";
	    //TODO 主键判断
        Serializable id = operator.add(parser.getAddSql(tableName, data),primaryKey);
		try {
			Field f;
			f = data.getClass().getDeclaredField(primaryKey);
			f.setAccessible(true);
			if(id!=null){
			    if(f.getType()==Integer.TYPE||f.getType()==Integer.class){
			        f.set(data, (Integer.parseInt(id.toString())));
			    }else if(f.getType()==Long.TYPE||f.getType()==Long.class){
			        f.set(data, (Long.parseLong(id.toString())));
			    }else if(f.getType()==String.class){
                    f.set(data, (id.toString()));
                }
			}
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		}  catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
	    return id;
	}

	@Override
	public int modify(String tableName, Domain domain) {
		return operator.update(parser.getUpdateSql(tableName, domain));
	}

	@Override
	public <T> T getById(String tableName, Serializable id) {
		return getById(tableName, OrmContext.getDomainClass(tableName), id);
	}
	private  <T> T getById(String tableName, Class clazz,Serializable id) {
	    if(id==null){
	        return null;
	    }
		return (T) generateObject(clazz, OrmContext
				.getAttributes(tableName), queryForMap(parser.getSelectHql(new Conditions(tableName).putEW(
						"id", id))));
	}

	@Override
	public boolean saveOrUpdate(String tableName, List<? extends Domain> datas) {
		ArrayList addList=new ArrayList(datas.size()/2);
		ArrayList updateList=new ArrayList(datas.size()/2);
		for(int i=0;i<datas.size();i++){
			if(checkIdExist(datas.get(i))){
				updateList.add(datas.get(i));
			}else{
				addList.add(datas.get(i));
			}
		}
		boolean success=true;
		if(updateList.size()>0){
		    success=success&&modifyByBatch(tableName,updateList);
		}
		if(addList.size()>0){
		    success=success&&addByBatch(tableName,addList);
		}
		return success;
	}

	@Override
	public void saveOrUpdate(String tableName, Domain data) {
		if(checkIdExist(data)){
			this.modify(tableName,data);
		}else{
			this.add(tableName,data);
		}
	}

	@Override
	public void execute(String sql) {
		operator.execute(sql);
	}

	@Override
	public boolean modifyByBatchWithColumn(List<? extends Domain> dataList, String[] cols) {
		if(dataList==null||dataList.size()==0)return false;
		return modifyByBatchWithColumn(getTableName(dataList.get(0)),dataList, cols);
	}

	@Override
	public boolean modifyByBatchWithColumn(String tableName, List<? extends Domain> dataList,
			String[] cols) {
		if(dataList==null||dataList.size()==0){
			return false;
		}
		if(cols==null||cols.length==0){
			return false;
		}
		Domain domain = dataList.get(0);
		if(DbConfig.getDatabaseType()==DbConfig.DB_TYPE_MYSQL){
			//可以采用批量更新
			BatchSqlHolder sqlHolder = parser.getBatchUpdateSql(tableName,cols,dataList);
			return operator.batchUpdate(sqlHolder);
		}else{
			Set<String> set = new HashSet<String>();
			for(String col:cols){
				set.add(col);
			}
			for(Domain data:dataList){
				data.setUpdateField(set);
			    modify(tableName,data);
			}
		}
		return true;
	}

	@Override
	public String getGroupConcat(Conditions conditions, String attribute,
			String separator) {
		String sql = parser.getGroupConcatSql(conditions,attribute,separator);
		return operator.queryForObject(sql, String.class);
	}
    @Override
    public Double getDouble(Conditions conditions) {
        return operator.queryForObject(parser.getSelectHql(conditions), Double.class);
    }
    @Override
    public Integer getInteger(Conditions conditions) {
        return operator.queryForObject(parser.getSelectHql(conditions), Integer.class);
    }
    @Override
    public Long getLong(Conditions conditions) {
        return operator.queryForObject(parser.getSelectHql(conditions), Long.class);
    }
    @Override
    public Date getDate(Conditions conditions) {
        return operator.queryForObject(parser.getSelectHql(conditions), Date.class);
    }
    @Override
    public Double getMin(Conditions conditions, String attribute) {
        conditions.setSelectValue("min("+attribute+")");
        return getDouble(conditions);
    }
    @Override
    public <T> T getMinDomain(Conditions conditions, String attribute) {
        conditions.putASC(attribute);
        return getOne(conditions);
    }
    @Override
    public <T> T getColumn(Conditions conditions, String attribute) {
        List<T> list = getColumnList(conditions, attribute);
        if(list.isEmpty()){
            return null;
        }
        return list.get(0);
    }
	@Override
	public boolean checkExist(Class clazz, String colName, Serializable value) {
		return checkExist(clazz, colName, value, 0);
	}
	@Override
	public boolean checkExist(String tableName, String colName,
			Serializable value) {
		return checkExist(tableName, colName, value, 0);
	}
	@Override
	public boolean checkMultiExist(Class clazz, Serializable... args) {
		return checkMultiExist(getTableName(clazz), args);
	}
	@Override
	public boolean checkMultiExistWithExceptId(Class clazz,
			Serializable exceptId, Serializable... args) {
		return checkMultiExistWithExceptId(getTableName(clazz), exceptId, args);
	}
	@Override
	public boolean checkMultiExist(String tableName, Serializable... args) {
		if(args==null||args.length%2!=0){
			throw new RuntimeException("参数错误");
		}
		Conditions conditions=new Conditions(tableName);
		for(int i=0;i<args.length;i++){
			conditions.putEW(args[i], args[i+1]);
			i++;
		}
		return this.getCount(conditions)>0;
	}
	@Override
	public boolean checkMultiExistWithExceptId(String tableName,
			Serializable exceptId, Serializable... args) {
		if(args==null||args.length%2!=0){
			throw new RuntimeException("参数错误");
		}
		Conditions conditions=new Conditions(tableName);
		for(int i=0;i<args.length;i++){
			conditions.putEW(args[i], args[i+1]);
			i++;
		}
		return this.getCount(conditions.putUE("id", exceptId))>0;
	}
	/**
	 * 获得domainControl,没有则返回null
	 * @return
	 */
	private IDomainControl getDomainControl(){
		return Config.getConfig().configDomainControl();
	}

	/**
	 * 获得表名字
	 * @param domain
	 * @return
	 */
	private String getTableName(Domain domain){
		String tableName;
		if(getDomainControl()!=null){
			tableName=getDomainControl().getDomainName(domain.getClass());
		}else{
			tableName=domain.getClass().getSimpleName();
		}
		return tableName;
	}
	/**
	 * 获得表名字
	 * @param domain
	 * @return
	 */
	private String getTableName(Class clazz){
		String tableName;
		if(getDomainControl()!=null){
			tableName=getDomainControl().getDomainName(clazz);
		}else{
			tableName=clazz.getSimpleName();
		}
		return tableName;
	}
	@Override
	public boolean checkMultiExistByOr(Class clazz, Serializable... args) {
		return checkMultiExistByOr(getTableName(clazz), args);
	}
	@Override
	public boolean checkMultiExistWithExceptIdByOr(Class clazz,
			Serializable exceptId, Serializable... args) {
		return checkMultiExistWithExceptIdByOr(getTableName(clazz), exceptId,args);
	}
	@Override
	public boolean checkMultiExistByOr(String tableName, Serializable... args) {
		if(args==null||args.length%2!=0){
			throw new RuntimeException("参数错误");
		}
		Conditions conditions=new Conditions(tableName);
		conditions.parenthesesStart();
		for(int i=0;i<args.length;i++){
			conditions.putEW(args[i], args[i+1]);
			conditions.or();
			i++;
		}
		conditions.parenthesesEnd();
		return this.getCount(conditions)>0;
	}
	@Override
	public boolean checkMultiExistWithExceptIdByOr(String tableName,
			Serializable exceptId, Serializable... args) {

		if(args==null||args.length%2!=0){
			throw new RuntimeException("参数错误");
		}
		Conditions conditions=new Conditions(tableName);
		conditions.parenthesesStart();
		for(int i=0;i<args.length;i++){
			conditions.putEW(args[i], args[i+1]);
			conditions.or();
			i++;
		}
		conditions.parenthesesEnd();
		conditions.setLink(Conditions.AND);
		conditions.putUE("id", exceptId);
		return this.getCount(conditions)>0;
	}

}
